Passa al contenuto principale
Versione: 2025-26

Sintassi per testbench

Le testbench in Verilog sono moduli utilizzati per simulare e testare il comportamento di reti digitali (il "device under test" o DUT). A differenza delle reti sintetizzabili, le testbench utilizzano costrutti non sintetizzabili come initial, $display, $finish, ecc., che servono solo per la simulazione. In questa pagina documentiamo le principali sintassi e strutture utilizzate nelle testbench, con particolare attenzione a quelle comuni negli esercizi d'esame.

informazioni

Non è necessario saper scrivere testbench, ma è utile saperle leggere per capire cosa succede durante una simulazione e debuggare in modo più efficace.

Struttura base di una testbench

Una testbench tipica è un modulo senza porte (input/output), che istanzia il DUT e genera stimoli per testarlo.

module testbench();
// Dichiarazioni di segnali (reg per input, wire per output)
reg input_signal;
wire output_signal;

// Istanziazione del DUT
DUT dut (
.input_signal(input_signal),
.output_signal(output_signal)
);

// Blocco initial per stimoli
initial begin
// Genera waveform
$dumpfile("waveform.vcd");
$dumpvars;

// Stimoli
input_signal = 0;
#10; // Aspetta 10 unità di tempo
input_signal = 1;
#10;

// Fine simulazione
$finish;
end
endmodule

La keyword initial introduce un blocco eseguito a partire dal tempo 0 della simulazione. I comandi $dumpfile e $dumpvars servono a generare il file VCD da leggere con GTKWave. Il comando $finish termina la simulazione.

Generatore di clock

Per reti sequenziali, è necessario un segnale di clock. Si usa un modulo separato clock_generator:

module clock_generator(
clock
);
output clock;

parameter HALF_PERIOD = 5;

reg CLOCK;
assign clock = CLOCK;

initial CLOCK <= 0;
always #HALF_PERIOD CLOCK <= ~CLOCK;
endmodule

Nell'istanziazione:

wire clock;
clock_generator clk(.clock(clock));

Test di input-output

Usiamo if per verificare output e $display per stampare eventuali errori.

if (output_signal !== expected) begin
$display("Wrong output: expected %d, got %d instead", expected, output_signal);
end

Notare che non c'è alcuna stampa se l'output è corretto, quindi una simulazione che non stampa nulla è una prima indicazione (ma mai garanzia) di aver implementato correttamente la rete richiesta.

Per testare più casi, usiamo cicli for e funzioni automatic per generare testcase.

function automatic [15:0] get_testcase;
input [7:0] i;

reg [7:0] in;
reg [7:0] out;

begin
// Logica per generare {in, out} basato su i
in = i * 10;
out = in * 2;

get_testcase = {in, out};
end
endfunction

initial begin
...
reg [7:0] i;
reg [7:0] test_in;
reg [7:0] expected_out;

for (i = 0; i < 64; i++) begin
{test_in, expected_out} = get_testcase(i);
// Stimolo test_in a dut; controllo output
end
...
end

Linee di errore

Per evidenziare errori nelle waveform, usiamo variabili come reg error e un blocco always.

reg error;

// Reset di error a inizio simulazione
initial error = 0;

// Ogni volta che error va a 1, lo resettiamo poco dopo
always @(posedge error) #1 error = 0;

initial begin
...
if (condizione_errore) begin
$display("Errore rilevato");
error = 1;
end
...
end

Così facendo, nella waveform la linea error ci indica con degli impulsi facili da notare i punti della simulazione in cui sono stati rilevati errori.

Strutture avanzate: concorrenza e timeout

Quando la rete DUT deve interagire con più dispositivi indipendenti è necessario simulare questi in modo parallelo, per non introdurre temporizzazioni forzate. L'esempio più comune sono produttori e consumatori, ciascuno con i propri handshake da tenere indipendenti dagli altri. Usiamo per questo fork...join.

fork
begin : dispositivo_A
// Logica che simula la rete A
// Eventuale verifica degli output da DUT ad A
end
begin : dispositivo_B
// Logica che simula la rete B
// Eventuale verifica degli output da DUT a B
end
join

Un blocco fork...join esegue finché non sono terminati tutti i suoi sottoprocessi, oppure viene terminato anticipatamente con disable.

Sempre legato all'handshake c'è il problema del timeout: una rete DUT che non esegue correttamente i propri handshake porta a una simulazione che si blocca su attese infinite. Utilizziamo quindi fork...join anche per introdurre un limite massimo di esecuzione per terminare le simulazioni evidentemente troppo lunghe.

initial begin
...
fork : f
begin
#100000; // Attesa molto molto lunga
$display("Timeout - waiting for signal failed");
disable f; // termina tutto il fork, procedendo alla $finish
end
begin
// Processo principale, con eventuali fork...join per dispositivi
end
join

$finish;
end